import json
from contextlib import contextmanager
from datetime import datetime

from flask import current_app
from flask_sqlalchemy import SQLAlchemy as _SQLAlchemy, BaseQuery, SignallingSession
from sqlalchemy import inspect, orm, Column, Integer, SmallInteger, DateTime

from app import constants
from libs.error_code import NotFound, SetError, ParmesError
from libs.utils import json_has_key, try_pass, format_datetime


class SQLAlchemy(_SQLAlchemy):

    def create_session(self, options):
        options['autoflush'] = False
        return orm.sessionmaker(class_=SignallingSession, db=self, **options)

    @contextmanager
    def auto_commit(self, type='set', message=None):
        try:
            yield
            self.session.commit()
        except Exception as e:
            print(e)
            db.session.rollback()
            if type == 'set':
                _message = message or '更新异常'
                if hasattr(e, 'message'):
                    _message = e.message
                raise ParmesError(message=_message)
            if type == 'create':
                _message = message or '新增异常'
                if hasattr(e, 'message'):
                    _message = e.message
                raise ParmesError(message=_message)
            if type == 'delete':
                _message = message or '删除异常'
                if hasattr(e, 'message'):
                    _message = e.message
                raise ParmesError(message=_message)
            raise e


class Query(BaseQuery):
    def filter_by(self, **kwargs):
        # if 'status' not in kwargs.keys():
        #     kwargs['status'] = 1
        return super(Query, self).filter_by(**kwargs)

    def get_or_404(self, ident):
        if not ident:
            raise NotFound()
        rv = self.get(ident)
        if not rv:
            raise NotFound()
        return rv

    def first_or_404(self):
        rv = self.first()
        if not rv:
            raise NotFound()
        return rv


db = SQLAlchemy(query_class=Query)


class Base(db.Model):
    __abstract__ = True
    update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)  # 记录的更新时间
    updator = db.Column(db.String(36))
    create_time = db.Column(db.DateTime, default=datetime.now)
    creator = db.Column(db.String(36))

    __table_args__ = {"useexisting": True}

    def to_json(self):
        json_post = {}
        if hasattr(self, 'show_items'):
            for item in self.show_items:
                if hasattr(self, item):
                    json_post[item] = self[item]
        return json_post

    def to_kv_json(self):
        show_items = ('id', 'name')
        json_post = {}
        for item in show_items:
            if hasattr(self, item):
                json_post[item] = self[item]
        return json_post

    def __getitem__(self, item):
        return getattr(self, item)

    @classmethod
    def gets(cls, ids):
        return cls.query.filter(cls.id.in_(ids)).all()

    @classmethod
    def init_db(cls):
        if hasattr(cls, 'init_items'):
            for item in cls.init_items:
                _item = cls.query.filter(cls.name == item).first()
                if not _item:
                    _item = cls(name=item)
                    db.session.add(_item)
            db.session.commit()

    @classmethod
    def create(cls, json_post, user=None):
        if not hasattr(cls, 'create_items'):
            raise NotFound(message='未定义创建字段')
        json_has_key(json_post, cls.create_items)
        with db.auto_commit(type='create'):
            tz = cls()
            if hasattr(tz, 'creator'):
                if user:
                    tz.creator = user.id
            tz.set_attrs(json_post, user)
            db.session.add(tz)
        return tz

    @classmethod
    def update_or_create(cls, **kwargs):
        defaults = kwargs.pop('defaults')
        with db.auto_commit(type='create'):
            obj = cls.query.filter_by(**kwargs).first()
            if not obj:
                obj = cls(**kwargs)
            for key, value in defaults.items():
                if hasattr(cls, key):
                    setattr(obj, key, value)
            db.session.add(obj)
        return obj

    @property
    def creator_show(self):
        from app.models import User
        return User.to_view(self.creator)

    @property
    def create_time_show(self):
        return format_datetime(self.create_time)

    @property
    def status_show(self):
        if hasattr(self, 'status_map') and hasattr(self, 'status'):
            return self.status_map.get(self.status, '未定义')
        return ""

    @classmethod
    def to_view(cls, id):
        if id:
            try:
                resp_json = current_app.redis_store.get(id)
                if resp_json is not None:
                    return json.loads(resp_json)
            except Exception as e:
                current_app.logger.error(e)
            item = cls.query.get(id)
            if item:
                data = {'id': item.id}
                if hasattr(item, 'name'):
                    data['name'] = item.name
                if hasattr(item, 'flag'):
                    data['flag'] = item.flag
                if hasattr(item, 'level'):
                    data['level'] = item.level
                if hasattr(item, 'title'):
                    data['title'] = item.title
                current_app.redis_store.setex(id, constants.VIEW_INFO_REDIS_CACHE_EXPIRES, json.dumps(data))
                return data
        return None

    def del_redis_record(self, flag=None):
        with try_pass():
            if flag:
                current_app.redis_store.delete(flag)
            else:
                current_app.redis_store.delete(self.id)

    def set_attrs(self, attrs_dict, user=None, message=None):
        with db.auto_commit('set', message=message):
            if user:
                self.updator = user.id
            for key, value in attrs_dict.items():
                if hasattr(self, 'update_items'):
                    if hasattr(self, key) and key in self.update_items:
                        self.__setattr__(key, value)
                else:
                    if hasattr(self, key) and key != 'id':
                        setattr(self, key, value)
            self.del_redis_record()

    def keys(self):
        return self.fields

    def save(self):
        db.session.add(self)
        db.session.commit()
